# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

from coin.base.book import BookBuilderBase
from coin.exchange.binance.kr_rest.product import BinanceProduct


class BookEntry(object):
  def __init__(self, price, qty):
    self.price = float(price)
    self.qty = float(qty)


class BinanceBookBuilderBase(BookBuilderBase):
  def __init__(self, native_symbol, last_update_id: int, book_builder_observer=None):
    super().__init__("Binance", native_symbol, book_builder_observer=book_builder_observer)
    self._last_update_id = last_update_id

  def reset_last_update_id(self, last_update_id):
    self._last_update_id = last_update_id

  def snapshot(self, bids: list, asks: list):
    self._ask_price_map.clear()
    self._bid_price_map.clear()

    for data in bids:
      if len(data) > 2:
        price, qty, dummy = data
      else:
        price, qty = data
      price = float(price)
      qty = float(qty)
      self._insert_bid(price, qty, insert_only=True)
    for data in asks:
      if len(data) > 2:
        price, qty, dummy = data
      else:
        price, qty = data
      price = float(price)
      qty = float(qty)
      self._insert_ask(price, qty, insert_only=True)

  def update(self, first_update_id: int, last_update_id: int, bids: list, asks: list):
    if last_update_id <= self._last_update_id:
      # Dropping: before the book
      pass
    else:
      assert last_update_id >= self._last_update_id + 1
      self._last_update_id = last_update_id
      self.update_impl(bids, asks)

  def update_impl(self, bids, asks):
    insert_max_bid_price = None
    insert_min_ask_price = None

    for data in bids:
      if len(data) > 2:
        price, qty, dummy = data
      else:
        price, qty = data
      price = float(price)
      qty = float(qty)
      if qty == 0:
        self._delete_bid(price)
      else:
        self._insert_bid(price, qty)
        if insert_max_bid_price is None or price > insert_max_bid_price:
          insert_max_bid_price = price

    for data in asks:
      if len(data) > 2:
        price, qty, dummy = data
      else:
        price, qty = data
      price = float(price)
      qty = float(qty)
      if qty == 0:
        self._delete_ask(price)
      else:
        self._insert_ask(price, qty)
        if insert_min_ask_price is None or price < insert_min_ask_price:
          insert_min_ask_price = price

    if (insert_max_bid_price is not None and self.ask0() is not None
        and insert_max_bid_price >= self.ask0().price):
      self._delete_ask_up_to_and_including(insert_max_bid_price)
    if (insert_min_ask_price is not None and self.bid0() is not None
        and insert_min_ask_price <= self.bid0().price):
      self._delete_bid_up_to_and_including(insert_min_ask_price)

  def _insert_bid(self, price, qty, insert_only=False):
    assert price > 0
    assert qty > 0
    entry = BookEntry(price, qty)
    if insert_only:
      assert price not in self._bid_price_map
    self._bid_price_map[price] = entry

  def _insert_ask(self, price, qty, insert_only=False):
    assert price > 0
    assert qty > 0
    entry = BookEntry(price, qty)
    if insert_only:
      assert price not in self._ask_price_map
    self._ask_price_map[price] = entry

  def _delete_bid(self, price):
    if price in self._bid_price_map:
      del self._bid_price_map[price]

  def _delete_ask(self, price):
    if price in self._ask_price_map:
      del self._ask_price_map[price]

  def _delete_bid_up_to_and_including(self, price):
    elist = self._bid_price_map.get_entries(price, float('Inf'), exclude_max=False)
    plist = [p.price for p in elist]
    [self._delete_bid(p) for p in plist]
    return

  def _delete_ask_up_to_and_including(self, price):
    elist = self._ask_price_map.get_entries(-float('Inf'), price, exclude_max=False)
    plist = [p.price for p in elist]
    [self._delete_ask(p) for p in plist]
    return


class BinanceBookBuilder(BinanceBookBuilderBase):
  ProductType = BinanceProduct
